﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Art;

namespace System.Windows.Elements
{
    public class Palette : FrameworkElement
    {
        public const Double DefaultMarkerSize = 8.5;
        public static readonly DependencyProperty PickedColorProperty, InverseMixerProperty, MarkerSizeProperty, IsMarkerVisibleProperty;
        public static readonly RoutedEvent ColorPickedEvent = EventManager.RegisterRoutedEvent("ColorPicked", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<IColor>), typeof(Palette));

        private PaletteMarker marker;
        private PaletteChrome chrome;
        private FrameworkElement leftSlider, topSlider, rightSlider, bottomSlider;

        static Palette()
        {
            FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(Palette), new FrameworkPropertyMetadata(typeof(Palette)));

            FrameworkPropertyMetadata metadata;
            Palette.InverseMixerProperty = DependencyProperty.Register("InverseMixer", typeof(Func<IColor, Point>), typeof(Palette), new FrameworkPropertyMetadata() { AffectsRender = true });

            metadata = new FrameworkPropertyMetadata();
            metadata.DefaultValue = RgbColor.FromScRgb(1.0, 0.0, 0.0, 0.0);
            metadata.AffectsRender = true;
            Palette.PickedColorProperty = DependencyProperty.Register("PickedColor", typeof(IColor), typeof(Palette), metadata);

            Palette.MarkerSizeProperty = DependencyProperty.Register("MarkerSize", typeof(Double), typeof(Palette), new FrameworkPropertyMetadata(Palette.DefaultMarkerSize) { AffectsRender = true });
            Palette.IsMarkerVisibleProperty = DependencyProperty.Register("IsMarkerVisibleProperty", typeof(Boolean), typeof(Palette), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        }

        public Palette()
        {
            this.chrome = new PaletteChrome();
            this.AddVisualChild(this.chrome);
            this.AddLogicalChild(this.chrome);

            this.marker = new PaletteMarker() { Stroke = new Pen(Brushes.White, 1.0) };
            this.marker.SetBinding(PaletteMarker.VisibilityProperty, new Binding("IsMarkerVisibleProperty") { Source = this, Mode = BindingMode.OneWay, Converter = new BooleanToVisibilityConverter() });
            this.marker.SetBinding(PaletteMarker.WidthProperty, new Binding("MarkerSize") { Source = this, Mode = BindingMode.OneWay });
            this.marker.SetBinding(PaletteMarker.HeightProperty, new Binding("MarkerSize") { Source = this, Mode = BindingMode.OneWay });

            this.AddVisualChild(this.marker);
            this.AddLogicalChild(this.marker);
        }

        protected override Int32 VisualChildrenCount { get { return 6; } }

        protected override Visual GetVisualChild(Int32 index)
        {
            switch (index)
            {
                case 0: return this.chrome;
                case 1: return this.marker;
                case 2: return this.leftSlider;
                case 3: return this.topSlider;
                case 4: return this.rightSlider;
                case 5: return this.bottomSlider;
                default: return null;
            }
        }

        private static Size MeasureSliders(Size availableSize, params FrameworkElement[] sliders)
        {
            var desiredSliderSize = new Size();
            for (Int32 i = 0; i < sliders.Length; i++)
            {
                var slider = sliders[i];
                if (slider != null)
                {
                    slider.Measure(availableSize);
                    desiredSliderSize.Width += slider.DesiredSize.Width;
                    desiredSliderSize.Height += slider.DesiredSize.Height;
                }
            }
            return desiredSliderSize;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            // 6 - 88 - 6
            var availableSliderSize = new Size(availableSize.Width * 0.06, availableSize.Height * 0.06);
            var desiredSliderSize = Palette.MeasureSliders(availableSliderSize, this.leftSlider, this.topSlider, this.rightSlider, this.bottomSlider);

            var dx = availableSize.Width - desiredSliderSize.Width;
            var dy = availableSize.Height - desiredSliderSize.Height;
            if (dx < 0) dx = 0;
            if (dy < 0) dy = 0;
            this.chrome.Measure(new Size(dx, dy));

            return availableSize;
        }

        private Thickness CalculateSliderMargin()
        {
            Double leftMargin = 0.0, topMargin = 0.0, rightMargin = 0.0, bottomMargin = 0.0;
            if (this.leftSlider != null)
            {
                var temp = this.leftSlider.ActualHeight;
                leftMargin = Math.Max(leftMargin, this.leftSlider.ActualWidth);
                topMargin = Math.Max(topMargin, temp / 2);
                bottomMargin = Math.Max(bottomMargin, temp / 2);
            }
            if (this.topSlider != null)
            {
                var temp = this.topSlider.ActualWidth;
                topMargin = Math.Max(topMargin, this.topSlider.ActualHeight);
                leftMargin = Math.Max(leftMargin, temp / 2);
                rightMargin = Math.Max(rightMargin, temp / 2);
            }
            if (this.rightSlider != null)
            {
                var temp = this.rightSlider.ActualHeight;
                rightMargin = Math.Max(rightMargin, this.rightSlider.ActualWidth);
                topMargin = Math.Max(topMargin, temp / 2);
                bottomMargin = Math.Max(bottomMargin, temp / 2);
            }
            if (this.bottomSlider != null)
            {
                var temp = this.bottomSlider.ActualWidth;
                bottomMargin = Math.Max(bottomMargin, this.bottomSlider.ActualHeight);
                leftMargin = Math.Max(leftMargin, temp / 2);
                rightMargin = Math.Max(rightMargin, temp / 2);
            }
            
            return new Thickness(leftMargin, topMargin, rightMargin, bottomMargin);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (finalSize.Width == 0 || finalSize.Height == 0) return base.ArrangeOverride(finalSize);
            else
            {
                var margin = this.CalculateSliderMargin();

                var chromeWidth = finalSize.Width - margin.Left - margin.Right;
                var chromeHeight = finalSize.Height - margin.Top - margin.Bottom;
                if (chromeWidth < 0) chromeWidth = 0;
                if (chromeHeight < 0) chromeHeight = 0;

                this.chrome.Arrange(new Rect(margin.Left, margin.Top, chromeWidth, chromeHeight));

                var inverseMixer = this.InverseMixer;
                Point? pickedLocation = null;
                if (inverseMixer != null) pickedLocation = inverseMixer(this.PickedColor);

                if (pickedLocation == null)
                {
                    //this.chrome.Arrange(new Rect(new Point(), finalSize));
                    this.marker.Arrange(new Rect());
                    if (this.leftSlider != null) this.leftSlider.Arrange(new Rect());
                    if (this.topSlider != null) this.topSlider.Arrange(new Rect());
                    if (this.rightSlider != null) this.rightSlider.Arrange(new Rect());
                    if (this.bottomSlider != null) this.bottomSlider.Arrange(new Rect());
                }
                else
                {
                    //this.chrome.Arrange(new Rect(margin.Left, margin.Top, chromeWidth, chromeHeight));

                    var x = margin.Left + pickedLocation.Value.X * chromeWidth;
                    var y = margin.Top + pickedLocation.Value.Y * chromeHeight;

                    if (this.leftSlider != null) this.leftSlider.Arrange(new Rect(margin.Left - this.leftSlider.ActualWidth, y - this.leftSlider.ActualHeight / 2, this.leftSlider.ActualWidth, this.leftSlider.ActualHeight));
                    if (this.topSlider != null) this.topSlider.Arrange(new Rect(x - this.topSlider.ActualWidth / 2, margin.Top - this.topSlider.ActualHeight, this.topSlider.ActualWidth, this.topSlider.ActualHeight));
                    if (this.rightSlider != null) this.rightSlider.Arrange(new Rect(margin.Left + chromeWidth, y - this.rightSlider.ActualHeight / 2, this.rightSlider.ActualWidth, this.rightSlider.ActualHeight));
                    if (this.bottomSlider != null) this.bottomSlider.Arrange(new Rect(x - this.bottomSlider.ActualWidth / 2, margin.Top + chromeHeight, this.bottomSlider.ActualWidth, this.bottomSlider.ActualHeight));
                    
                    if (this.IsMarkerVisible)
                    {
                        var lightness = ColorSpaces.Translate(this.PickedColor, ColorSpaces.Lab).ScLightness;
                        this.marker.Stroke.Brush = (lightness > 0.5) ? Brushes.Black : Brushes.White;

                        Double width = this.marker.Width, height = this.marker.Height;

                        var left = x + 1 - width / 2;
                        var top = y + 1 - height / 2;
                        Double overX = left + width - chromeWidth - margin.Left, overY = top + height - chromeHeight - margin.Top;

                        var clip = new Rect((left < margin.Left) ? margin.Left - left : 0.0, (top < margin.Top) ? margin.Top - top : 0.0, width, height);
                        if (overX > 0) clip.Width = Math.Max(0, width - overX);
                        if (overY > 0) clip.Height = Math.Max(0, height - overY);

                        var rectangle = new Rect(left, top, width, height);
                        this.marker.Clip = new RectangleGeometry(clip);
                        this.marker.Arrange(rectangle);
                    }
                }

                return finalSize;
            }
        }

        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseDown(e);
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.UpdateColorByLocation();
                this.CaptureMouse();
            }
        }

        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseUp(e);
            this.ReleaseMouseCapture();
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            this.ReleaseMouseCapture();
        }

        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            base.OnPreviewMouseMove(e);
            if (e.LeftButton == MouseButtonState.Pressed) this.UpdateColorByLocation();
        }

        private void UpdateColorByLocation()
        {
            Point position = Mouse.GetPosition(this.chrome);
            Double x = position.X / this.chrome.ActualWidth;
            Double y = position.Y / this.chrome.ActualHeight;
            if (x > 1) x = 1; else if (x < 0) x = 0;
            if (y > 1) y = 1; else if (y < 0) y = 0;

            var oldValue = this.PickedColor;
            var newValue = this.chrome.Mixer(new Point(x, y));

            this.PickedColor = newValue;
            this.OnColorPicked(oldValue, newValue);
        }

        protected virtual void OnColorPicked(IColor oldValue, IColor newValue)
        {
            RoutedPropertyChangedEventArgs<IColor> args = new RoutedPropertyChangedEventArgs<IColor>(oldValue, newValue);
            args.RoutedEvent = Palette.ColorPickedEvent;
            this.RaiseEvent(args);
        }

        public event RoutedPropertyChangedEventHandler<IColor> ColorPicked
        {
            add { this.AddHandler(Palette.ColorPickedEvent, value); }
            remove { this.RemoveHandler(Palette.ColorPickedEvent, value); }
        }

        #region Palette Chrome

        public Brush Background
        {
            get { return this.chrome.Background; }
            set { this.chrome.Background = value; }
        }

        public Func<Point, IColor> Mixer
        {
            get { return this.chrome.Mixer; }
            set 
            { 
                this.chrome.Mixer = value;
                this.InverseMixer = null;
            }
        }

        public Double ReductionFactor
        {
            get { return this.chrome.ReductionFactor; }
            set { this.chrome.ReductionFactor = value; }
        }

        public Boolean IsAlphaDisplayed
        {
            get { return this.chrome.IsAlphaDisplayed; }
            set { this.chrome.IsAlphaDisplayed = value; }
        }

        #endregion

        #region Sliders

        private void SetSlider(ref FrameworkElement slider, FrameworkElement value)
        {
            if (slider != null)
            {
                this.RemoveLogicalChild(slider);
                this.RemoveVisualChild(slider);
            }

            slider = value;
            this.AddVisualChild(value);
            this.AddLogicalChild(value);
        }

        public FrameworkElement LeftSlider
        {
            get { return this.leftSlider; }
            set { this.SetSlider(ref this.leftSlider, value); }
        }

        public FrameworkElement TopSlider
        {
            get { return this.topSlider; }
            set { this.SetSlider(ref this.topSlider, value); }
        }

        public FrameworkElement RightSlider
        {
            get { return this.rightSlider; }
            set { this.SetSlider(ref this.rightSlider, value); }
        }

        public FrameworkElement BottomSlider
        {
            get { return this.bottomSlider; }
            set { this.SetSlider(ref this.bottomSlider, value); }
        }

        #endregion

        public IColor PickedColor
        {
            get { return (IColor)this.GetValue(Palette.PickedColorProperty); }
            set { this.SetValue(Palette.PickedColorProperty, value); }
        }

        public Func<IColor, Point> InverseMixer
        {
            get { return (Func<IColor, Point>)this.GetValue(Palette.InverseMixerProperty); }
            set { this.SetValue(Palette.InverseMixerProperty, value); }
        }

        public Double MarkerSize
        {
            get { return (Double)this.GetValue(Palette.MarkerSizeProperty); }
            set { this.SetValue(Palette.MarkerSizeProperty, value); }
        }

        public Boolean IsMarkerVisible
        {
            get { return (Boolean)this.GetValue(Palette.IsMarkerVisibleProperty); }
            set { this.SetValue(Palette.IsMarkerVisibleProperty, value); }
        }
    }
}
